package com.hup.winHolderService.ws.handler;

import com.alibaba.fastjson.JSON;
import com.hup.winHolderService.framework.exception.AppLogger;
import com.hup.winHolderService.framework.exception.AppServerException;
import com.hup.winHolderService.ws.WsClient;
import com.hup.winHolderService.ws.handler.parser.WsDataParser;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * websocket的[请求处理,数据发送]处理类:
 * .处理请求: {@link WsRequestMethod}  {@link WsRequestMethodData}
 * .发送数据: 如序列化数据?? 未实现
 *
 * @author hugan
 * @date 2023/10/8
 */
@Slf4j
public class WsDataHandler {

    private final Map<String, WsRequestMethodData> requestMethodMap = new HashMap<>();
    private final HashMap<Class<?>, Object> dataParserCache = new HashMap<>();

    /**
     * 扫描 service 类下的 {@link WsRequestMethod} 方法
     */
    public void addService(Object service) {
        Class<?> clz = service.getClass();
        if (Modifier.isPrivate(clz.getModifiers())) {
            throw new IllegalArgumentException("处理类必须是[public]");
        }

        //遍历 所有public方法,包含父类的
        for (Method method : clz.getMethods()) {
            WsRequestMethod wsRequestMethod = method.getAnnotation(WsRequestMethod.class);
            if (wsRequestMethod == null) continue;

            //分析带 WsRequestMethod 注解的方法
            WsRequestMethodData requestMethod = new WsRequestMethodData();
            String apiPath = wsRequestMethod.value();
            if (apiPath.isEmpty() || apiPath.length() > Byte.MAX_VALUE) {
                throw new IllegalArgumentException("websocket: api路径异常[" + apiPath + "], 要求[1," + Byte.MAX_VALUE + "]");
            }
            //入参Index: WsRequestData
            if (!wsRequestMethod.noneRequestData()) {
                Annotation[][] parameterAnnotations = method.getParameterAnnotations();
                for (int i = parameterAnnotations.length - 1; i >= 0; i--) {
                    Optional<Annotation> optional = Arrays.stream(parameterAnnotations[i])
                            .filter(item -> item instanceof WsRequestData).findFirst();
                    if (optional.isPresent()) {
                        requestMethod.setDataIndex(i);
                        requestMethod.setDataClz(method.getParameterTypes()[i]);
                        break;
                    }
                }
                if (requestMethod.getDataIndex() == null) {
                    log.warn("缺少[WsRequestData]注解,\nmethod={} {}", method, AppLogger.getStack(new Throwable()));
                    continue;
                }
            }

            //入参Index: WsClient
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = parameterTypes.length - 1; i >= 0; i--) {
                Class<?> type = parameterTypes[i];
                if (type.equals(WsClient.class)) {
                    requestMethod.setClientIndex(i);
                    break;
                }
            }
            //其他参数
            requestMethod.setService(service);
            requestMethod.setMethod(method);
            requestMethod.setApiPath(apiPath);
            requestMethod.setParamLength(method.getParameterCount());
            WsDataParser dataParser = (WsDataParser) dataParserCache.computeIfAbsent(wsRequestMethod.dataParser(),
                    aClass -> {
                        log.info("实例化WsDataParser:{}", aClass.getName());
                        return JSON.parseObject("{}", aClass);
                    });
            requestMethod.setDataParser(dataParser);
            if (requestMethodMap.containsKey(apiPath)) {
                throw new IllegalArgumentException("apiPath重复=" + apiPath
                        + "\n" + requestMethod
                        + "\n" + requestMethodMap.get(apiPath));
            }
            requestMethodMap.put(apiPath, requestMethod);
            //log.info("WsRequestMethod:{}", requestMethod);
        }
    }

    /**
     * @param msg 格式: [0]路径长度m, [1-(m+1)]api路径, [后面]入参数据
     * @return nullable-没匹配到处理方法或msg格式异常,类似404或入参异常
     */
    public String getApiPath(byte[] msg, WsClient client) {
        byte pathLength = msg[0];
        if (1 + pathLength > msg.length) {
            log.warn("请求体异常: pathLength={}, byte={}字节, user={}", pathLength, msg.length, client.getUser());
            return null;
        }
        String apiPath = new String(msg, 1, pathLength, StandardCharsets.UTF_8);
        if (!requestMethodMap.containsKey(apiPath)) {
            //类似404
            log.warn("未知消息: apiPath={}, byte={}字节, user={}", apiPath, msg.length, client.getUser());
            return null;
        }
        return apiPath;
    }

    /**
     * 处理websocket onMessage请求
     *
     * @param msg 格式: [0]路径长度m, [1-(m+1)]api路径, [后面]入参数据
     */
    public void handleRequest(byte[] msg, WsClient client) {
        String apiPath = getApiPath(msg, client);
        if (apiPath == null) return;
        WsRequestMethodData method = requestMethodMap.get(apiPath);
        method.doInvoke(msg, client);
    }

    @Data
    private static class WsRequestMethodData {

        private Object service;
        private Method method;
        private String apiPath;
        private int paramLength;
        private WsDataParser dataParser;

        private Integer dataIndex;
        private Class<?> dataClz;

        private Integer clientIndex;

        public void doInvoke(byte[] msg, WsClient client) {
            try {
                Object[] args = new Object[paramLength];
                if (dataIndex != null) args[dataIndex] = dataParser.parse(msg, dataClz);
                if (clientIndex != null) args[clientIndex] = client;
                method.invoke(service, args);
            } catch (InvocationTargetException e) {
                new AppServerException("ws消息处理异常", e.getCause()).doLog();
            } catch (Throwable e) {
                String codeLoc = service.getClass().getName() + "." + method.getName() + "(" + service.getClass().getSimpleName() + ".java:0)";
                new AppServerException("ws消息处理异常\n\tat " + codeLoc, e).doLog();
            }
        }

        @Override
        public String toString() {
            return "WsRequestMethodData{" +
                    "apiPath=[" + apiPath +
                    "], method=" + method +
                    ", dataParser=" + dataParser.getClass() +
                    '}';
        }
    }

}
